﻿#pragma once
#include <cstdint>
#include <functional>

#define CAP_PER_INSTANCE 1000

namespace AutoScale {

	typedef enum
	{
		ScaleByPercent = 0,
		ScaleByCount = 1
	} ScaleType;

	typedef enum
	{
		RuleOpLess = 1,
		RuleOpLessOrEqual = 2,
		RuleOpGreater = 3,
		RuleOpGreaterOrEqual = 4
	} RuleOpcode;

	class ScaleRule
	{
	public:
		bool ApplyScale(int32_t loadPercentage, int32_t& instanceCount) const;
		static ScaleRule Make(ScaleType type, int32_t value, RuleOpcode op, int32_t threshold);
	private:
		ScaleType type_;
		int32_t value_;
		RuleOpcode opcode_;
		int32_t threshold_;
	};

	struct ServiceLoad
	{
		int32_t DurationSteps;
		int32_t RequestCount;
	};

	struct ServerStatus
	{
		int32_t InstanceCount;
		int32_t TotalRequests;

		int32_t LoadPercentage()
		{
			return TotalRequests * 100 / (InstanceCount * CAP_PER_INSTANCE);
		}
	};

	struct LoadStatus
	{
		int32_t RequestComing;
		int32_t RequestComplete;

		LoadStatus operator+ (LoadStatus const& rhs) const
		{
			return { RequestComing + rhs.RequestComing, RequestComplete + rhs.RequestComplete };
		}

		LoadStatus& operator+= (LoadStatus const& rhs)
		{
			RequestComing + rhs.RequestComing;
			RequestComplete + rhs.RequestComplete;
			return *this;
		}
	};

	class LoadGenerator
	{
	public:
		LoadGenerator();
		LoadGenerator(int32_t startStep, int32_t endStep, int32_t intervalStep, std::function<int32_t(int32_t, int32_t)>&& valueFunc, std::function<int32_t(void)>&& durationFunc);
		bool NextLoad(int32_t step, ServiceLoad& load);
		int32_t GetEndStep() const { return stepEnd_; }
	private:
		std::function<int32_t(int32_t, int32_t)> valueFunc_;
		std::function<int32_t(void)> durationFunc_;
		int32_t stepInterval_;
		int32_t stepStart_;
		int32_t stepEnd_;
	};

	class Simulator
	{
	public:
		Simulator(int32_t minInstance, int32_t maxInstance);
		~Simulator();
		void AddRule(ScaleRule const& rule, int32_t intervalSteps);
		void OneStep();
		ServerStatus GetServerStatus() const;
		LoadStatus GetRecentLoadStatus() const;
		bool Finished() const;
		void SetLoadGenerator(LoadGenerator&& generator);
	private:
		class SimulatorImpl;
		SimulatorImpl* internal_;
	};
}